﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace QDP.Platform.Silverlight.Controls
{
    /// <summary>
    /// Canvas containing floating wndows.
    /// </summary>
    public class FloatingWindowHost : Canvas
    {
        // Thickness of resizing area.
        private const double SnapinDistanceDefaultValue = 5.0;

        // Default size of the window's icon.
        private const double IconWidthDefaultValue = 120;
        private const double IconHeightDefaultValue = 70;

        // Default size of the iconbar item.
        private const double WindowIconWidthDefaultValue = 140;
        private const double WindowIconHeightDefaultValue = 100;

        // Animation duration in milliseconds
        private const double OverlayFadeInMilliseconds = 300;
        private const double OverlayFadeOutMilliseconds = 300;

        /// <summary>
        /// Current ZIndex of a child element
        /// </summary>
        private static int zIndex = 1;

        /// <summary>
        /// Iconbar containing minimized windows.
        /// </summary>
        private Iconbar iconBar = new Iconbar();

        /// <summary>
        /// Overlay used as a background for modal windows.
        /// </summary>
        private Grid overlay = new Grid();
 
        #region public bool SnapinEnabled

        /// <summary>
        /// Gets or sets a value indicating whether snap in is enabled.
        /// </summary>
        /// <value><c>true</c> if snap in is enabled; otherwise, <c>false</c>.</value>
        public bool SnapinEnabled
        {
            get { return (bool)GetValue(SnapinEnabledProperty); }
            set { SetValue(SnapinEnabledProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.SnapinEnabled" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.SnapinEnabled" /> dependency property.
        /// </value>
        public static readonly DependencyProperty SnapinEnabledProperty =
            DependencyProperty.Register(
            "SnapinEnabled",
            typeof(bool),
            typeof(FloatingWindowHost),
            new PropertyMetadata(true, null));

        #endregion

        #region public double SnapinDistance

        /// <summary>
        /// Gets or sets a value of the snap in distance.
        /// </summary>
        /// <value>Snap in distance.</value>
        public double SnapinDistance
        {
            get { return (double)GetValue(SnapinDistanceProperty); }
            set { SetValue(SnapinDistanceProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.SnapinDistance" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.SnapinDistance" /> dependency property.
        /// </value>
        public static readonly DependencyProperty SnapinDistanceProperty =
            DependencyProperty.Register(
            "SnapinDistance",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(SnapinDistanceDefaultValue, null));

        #endregion

        #region public double SnapinMargin

        /// <summary>
        /// Gets or sets a value of the snap in margin - distance between adjacent edges.
        /// </summary>
        /// <value>Snap in margin.</value>
        public double SnapinMargin
        {
            get { return (double)GetValue(SnapinMarginProperty); }
            set { SetValue(SnapinMarginProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.SnapinMargin" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.SnapinMargin" /> dependency property.
        /// </value>
        public static readonly DependencyProperty SnapinMarginProperty =
            DependencyProperty.Register(
            "SnapinMargin",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(0.0, null));

        #endregion

        #region public bool ShowMinimizedOnlyInIconbar

        /// <summary>
        /// Gets or sets a value indicating whether to show only minimized windows in the iconbar.
        /// </summary>
        /// <value><c>true</c> if to show only minimized windows in the iconbar; otherwise, <c>false</c>.</value>
        public bool ShowMinimizedOnlyInIconbar
        {
            get { return (bool)GetValue(ShowMinimizedOnlyInIconbarProperty); }
            set { SetValue(ShowMinimizedOnlyInIconbarProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.ShowMinimizedOnlyInIconbarProperty" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.ShowMinimizedOnlyInIconbarProperty" /> dependency property.
        /// </value>
        public static readonly DependencyProperty ShowMinimizedOnlyInIconbarProperty =
            DependencyProperty.Register(
            "ShowMinimizedOnlyInIconbar",
            typeof(bool),
            typeof(FloatingWindowHost),
            new PropertyMetadata(false, null));

        #endregion

        #region public double IconWidth

        /// <summary>
        /// Gets or sets the width of the window's icon.
        /// </summary>
        /// <value>The width of the window's icon.</value>
        public double IconWidth
        {
            get { return (double)GetValue(IconWidthProperty); }
            set { SetValue(IconWidthProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.IconWidth" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.IconWidth" /> dependency property.
        /// </value>
        public static readonly DependencyProperty IconWidthProperty =
            DependencyProperty.Register(
            "IconWidth",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(IconWidthDefaultValue, OnIconWidthPropertyChanged));

        /// <summary>
        /// IconWidthProperty PropertyChangedCallback call back static function.
        /// </summary>
        /// <param name="d">FloatingWindowHost object whose IconWidth property is changed.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs which contains the old and new values.</param>
        private static void OnIconWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FloatingWindowHost host = (FloatingWindowHost)d;
            host.iconBar.IconWidth = (double)e.NewValue;
        }

        #endregion

        #region public double IconHeight

        /// <summary>
        /// Gets or sets the height of the window's icon.
        /// </summary>
        /// <value>The height of the window's icon.</value>
        public double IconHeight
        {
            get { return (double)GetValue(IconHeightProperty); }
            set { SetValue(IconHeightProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.IconHeight" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.IconHeight" /> dependency property.
        /// </value>
        public static readonly DependencyProperty IconHeightProperty =
            DependencyProperty.Register(
            "IconHeight",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(IconHeightDefaultValue, OnIconHeightPropertyChanged));

        /// <summary>
        /// IconHeightProperty PropertyChangedCallback call back static function.
        /// </summary>
        /// <param name="d">FloatingWindowHost object whose IconHeight property is changed.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs which contains the old and new values.</param>
        private static void OnIconHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FloatingWindowHost host = (FloatingWindowHost)d;
            host.iconBar.IconHeight = (double)e.NewValue;
        }

        #endregion

        #region public double WindowIconWidth

        /// <summary>
        /// Gets or sets the width of the iconbar item.
        /// </summary>
        /// <value>The width of the window's icon.</value>
        public double WindowIconWidth
        {
            get { return (double)GetValue(WindowIconWidthProperty); }
            set { SetValue(WindowIconWidthProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.WindowIconWidth" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.WindowIconWidth" /> dependency property.
        /// </value>
        public static readonly DependencyProperty WindowIconWidthProperty =
            DependencyProperty.Register(
            "WindowIconWidth",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(WindowIconWidthDefaultValue, OnWindowIconWidthPropertyChanged));

        /// <summary>
        /// WindowIconWidthProperty PropertyChangedCallback call back static function.
        /// </summary>
        /// <param name="d">FloatingWindowHost object whose WindowIconWidth property is changed.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs which contains the old and new values.</param>
        private static void OnWindowIconWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FloatingWindowHost host = (FloatingWindowHost)d;
            host.iconBar.WindowIconWidth = (double)e.NewValue;
        }

        #endregion

        #region public double WindowIconHeight

        /// <summary>
        /// Gets or sets the height of the iconbar item.
        /// </summary>
        /// <value>The height of the window's icon.</value>
        public double WindowIconHeight
        {
            get { return (double)GetValue(WindowIconHeightProperty); }
            set { SetValue(WindowIconHeightProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.WindowIconHeight" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.WindowIconHeight" /> dependency property.
        /// </value>
        public static readonly DependencyProperty WindowIconHeightProperty =
            DependencyProperty.Register(
            "WindowIconHeight",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(WindowIconHeightDefaultValue, OnWindowIconHeightPropertyChanged));

        /// <summary>
        /// WindowIconHeightProperty PropertyChangedCallback call back static function.
        /// </summary>
        /// <param name="d">FloatingWindowHost object whose WindowIconHeight property is changed.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs which contains the old and new values.</param>
        private static void OnWindowIconHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FloatingWindowHost host = (FloatingWindowHost)d;
            host.iconBar.WindowIconHeight = (double)e.NewValue;
        }

        #endregion

        #region public Brush OverlayBrush

        /// <summary>
        /// Gets or sets the overlay color.
        /// </summary>
        /// <value>The overlay color.</value>
        public Brush OverlayBrush
        {
            get { return (Brush)GetValue(OverlayBrushProperty); }
            set { SetValue(OverlayBrushProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.OverlayBrush" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.OverlayBrush" /> dependency property.
        /// </value>
        public static readonly DependencyProperty OverlayBrushProperty =
            DependencyProperty.Register(
            "OverlayBrush",
            typeof(Brush),
            typeof(FloatingWindowHost),
            new PropertyMetadata(new SolidColorBrush(Color.FromArgb(0x90, 0x20, 0x20, 0x30)), OnOverlayBrushPropertyChanged));

        /// <summary>
        /// OverlayBrushProperty PropertyChangedCallback call back static function.
        /// </summary>
        /// <param name="d">FloatingWindowHost object whose OverlayBrush property is changed.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs which contains the old and new values.</param>
        private static void OnOverlayBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FloatingWindowHost host = (FloatingWindowHost)d;
            host.overlay.Background = (Brush)e.NewValue;
        }

        #endregion

        #region public int WindowsCount
        /// <summary>
        /// Gets number of windows to be shown in the iconbar.
        /// </summary>
        /// <value>Number of the windows.</value>
        public int WindowsCount
        {
            get
            {
                return FloatingWindows.Where(x => x.IsOpen && x.ShowInIconbar &&
                    !(ShowMinimizedOnlyInIconbar && x.WindowState != WindowState.Minimized)).Count();
            }
        }

        #endregion

        #region private bool ContainsOverlay

        /// <summary>
        /// Gets a value indicating whether the host contains overlay.
        /// </summary>
        /// <value><c>true</c> if the host contains overlay; otherwise, <c>false</c>.</value>
        private bool ContainsOverlay
        {
            get
            {
                return this.Children.Contains(overlay);
            }
        }

        #endregion

        public FloatingWindowHost()
        {
            iconBar.IconWidth = IconWidthDefaultValue;
            iconBar.IconHeight = IconHeightDefaultValue;
            iconBar.WindowIconWidth = WindowIconWidthDefaultValue;
            iconBar.WindowIconHeight = WindowIconHeightDefaultValue;

            overlay.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
            overlay.VerticalAlignment = System.Windows.VerticalAlignment.Top;
            overlay.Background = new SolidColorBrush(Color.FromArgb(0x90, 0x20, 0x20, 0x30));

            this.Children.Add(iconBar);
            this.SizeChanged += new SizeChangedEventHandler(FloatingWindowHost_SizeChanged);
            this.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(FloatingWindowHost_MouseLeftButtonDown);
        }

        #region Events

        /// <summary>
        /// Occurs when the active <see cref="FloatingWindow" /> is changed.
        /// </summary>
        public event EventHandler<ActiveWindowChangedEventArgs> ActiveWindowChanged;

        #endregion Events

        /// <summary>
        /// Gets the floating windows collection.
        /// </summary>
        /// <value>The floating windows collection.</value>
        public IEnumerable<FloatingWindow> FloatingWindows
        {
            get
            {
                return this.Children.OfType<FloatingWindow>();
            }
        }

        /// <summary>
        /// Adds the specified floating window to the collection of child elements of the canvas.
        /// </summary>
        /// <param name="window">The floating window.</param>
        /// <exception cref="ArgumentNullException">Floating window is null.</exception>
        public void Add(FloatingWindow window)
        {
            if (window == null)
                throw new ArgumentNullException("window");

            if(!this.Children.Contains(window))
                this.Children.Add(window);
        }

        /// <summary>
        /// Removes the specified floating window from the collection of child elements of the canvas.
        /// </summary>
        /// <param name="window">The floating window.</param>
        public void Remove(FloatingWindow window)
        {
            if (window != null)
            {
                this.Children.Remove(window);
            }
        }

        /// <summary>
        /// Closes all floating windows.
        /// </summary>
        public void CloseAllWindows()
        {
            this.Children.OfType<FloatingWindow>().ToList().ForEach(x => x.Close());
        }

        /// <summary>
        /// Shows the iconbar.
        /// </summary>
        public void ShowIconbar()
        {
            iconBar.ShowIconbar();
        }

        /// <summary>
        /// Hides the iconbar.
        /// </summary>
        public void HideIconbar()
        {
            iconBar.HideIconbar();
        }

        /// <summary>
        /// Gets Snap In bounds as bounds of the host and all open windows except the specified one.
        /// </summary>
        /// <param name="windowToExclude">The window to exclude from the list.</param>
        /// <returns>List of bounding rectangles.</returns>
        public IEnumerable<Rect> GetSnapinBounds(FloatingWindow windowToExclude)
        {
            List<Rect> bounds = new List<Rect>();

            // Add host bounds
            bounds.Add(new Rect(0, 0, ActualWidth, ActualHeight));

            if (!ContainsOverlay)
            {
                foreach (var window in FloatingWindows)
                {
                    if (window != windowToExclude && window.IsOpen)
                        bounds.Add(window.BoundingRectangle);
                }
            }

            return bounds;
        }

        /// <summary>
        /// Show or hide the bootstrap button if number of windows has changed.
        /// </summary>
        public void UpdateIconbar()
        {
            iconBar.SetBootstrapButtonVisible(WindowsCount > 0);
        }

        /// <summary>
        /// Shows the overlay.
        /// </summary>
        public void ShowOverlay()
        {
            SetTopmost(overlay);

            if (!ContainsOverlay)
            {
                this.Children.Add(overlay);
                overlay.FadeIn(OverlayFadeInMilliseconds);
            }
        }

        /// <summary>
        /// Removes the overlay.
        /// </summary>
        public void RemoveOverlay()
        {
            if (ContainsOverlay)
            {
                if (FloatingWindows.Count(x => x.IsOpen && x.IsModal) == 0)
                {
                    // If there are no more modal windows - remove the overlay
                    overlay.FadeOut(OverlayFadeOutMilliseconds,
                        (s, e) =>
                        {
                            this.Children.Remove(overlay);
                        });

                    FloatingWindow topmostWindow = GetTopmostWindow();
                    SetFocusToActiveWindow(topmostWindow);
                }
                else
                {
                    // Find a modal window with the highest ZIndex and make it topmost
                    var modalWindows = from window in FloatingWindows
                                       where window.IsModal && window.IsOpen
                                       select new
                                       {
                                           ModalWindow = window,
                                           ZIndex = Canvas.GetZIndex(window)
                                       };

                    var modalWindow = (from window in modalWindows
                                       orderby window.ZIndex descending
                                       select window.ModalWindow).FirstOrDefault();

                    SetTopmost(modalWindow);
                    SetFocusToActiveWindow(modalWindow);
                }
            }
        }

        /// <summary>
        /// Sets the specified floating window topmost, and set Focus on it.
        /// </summary>
        /// <param name="window">FloatingWindow to set topmost.</param>
        /// <exception cref="ArgumentNullException">FloatingWindow is null.</exception>
        public void SetTopmostWindow(FloatingWindow window)
        {
            if (window == null)
                throw new ArgumentNullException("window");

            FloatingWindow topmostWindow = GetTopmostWindow();
            if (topmostWindow == null || window != topmostWindow)
            {
                SetTopmost(window);

                SetFocusToActiveWindow(window);

                if (!window.TopMost && !window.IsModal)
                    ShowTopmostWindows();

                ActiveWindowChangedEventArgs e = new ActiveWindowChangedEventArgs(topmostWindow, window);
                OnActiveWindowChanged(e);
            }
        }

        /// <summary>
        /// Activates the topmost window and sets focus on it.
        /// </summary>
        public void ActivateTopmostWindow()
        {
            FloatingWindow topmostWindow = GetTopmostWindow();
            SetFocusToActiveWindow(topmostWindow);

            ActiveWindowChangedEventArgs e = new ActiveWindowChangedEventArgs(null, topmostWindow);
            OnActiveWindowChanged(e);
        }

        /// <summary>
        /// Raises the <see cref="FloatingWindowHost.ActiveWindowChanged" /> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnActiveWindowChanged(ActiveWindowChangedEventArgs e)
        {
            EventHandler<ActiveWindowChangedEventArgs> handler = ActiveWindowChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Gets the topmost open FloatingWindow.
        /// </summary>
        /// <returns>The topmost open FloatingWindow.</returns>
        private FloatingWindow GetTopmostWindow()
        {
            var topmost = (from window in FloatingWindows
                           where !window.TopMost && window.IsOpen && window.WindowState != WindowState.Minimized
                           select new
                           {
                               Window = window,
                               ZIndex = Canvas.GetZIndex(window)
                           }
                           ).ToList().OrderBy(x => x.ZIndex).LastOrDefault();

            return topmost != null ? topmost.Window : null;
        }

        /// <summary>
        /// Shows the topmost windows in front of other windows.
        /// </summary>
        private void ShowTopmostWindows()
        {
            FloatingWindows
                .Where(x => x.IsOpen && x.TopMost).ToList()
                .ForEach(x => SetTopmost(x));

            SetTopmostIconbar();
        }

        /// <summary>
        /// Sets the specified UIElement topmost.
        /// </summary>
        /// <param name="element">UIElement to set topmost.</param>
        /// <exception cref="ArgumentNullException">UIElement is null.</exception>
        private void SetTopmost(UIElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            zIndex++;
            Canvas.SetZIndex(element, zIndex);
        }

        /// <summary>
        /// Sets the iconbar in front of other windows.
        /// </summary>
        private void SetTopmostIconbar()
        {
            if (!ContainsOverlay)
            {
                zIndex++;
                Canvas.SetZIndex(iconBar, zIndex++);
            }
        }

        /// <summary>
        /// Attempts to set the focus on the FloatingWindow.
        /// </summary>
        /// <param name="window">The window.</param>
        private void SetFocusToActiveWindow(FloatingWindow window)
        {
            if (window != null && !window.TopMost)
            {
                window.Focus();
            }
        }

        /// <summary>
        /// Updates clipping region on host SizeChanged event.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.SizeChangedEventArgs"/> instance containing the event data.</param>
        private void FloatingWindowHost_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Clip = new RectangleGeometry()
            {
                Rect = new Rect(0, 0, ActualWidth, ActualHeight)
            };

            overlay.Width = ActualWidth;
            overlay.Height = ActualHeight;
        }

        /// <summary>
        /// Handles the MouseLeftButtonDown event of the FloatingWindowHost control. 
        /// Closes the Iconbar on mouse left click.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void FloatingWindowHost_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            HideIconbar();
        }
    }
}
